/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2007 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or ( at your option ) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
aLONG with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

Description:
============
This is a new Version of the UserInfoDialogPage, to fit my needs
*/

/**
 * System & local includes:
 **/
#include "commonheaders.h"
#include "dlgPropsheet.h"
#include "pspAll.h"
#include "ex_import/svcExImport.h"
#include "svcReminder.h"

/**
 * Miranda includes:
 **/
#include <m_protocols.h>
#include <m_icq.h>

namespace NContactDetailsPS {

#define UPDATEANIMFRAMES	20

// internal dialog message handler
#define M_CHECKONLINE				( WM_USER+10 )
#define HM_PROTOACK					( WM_USER+11 )
#define HM_SETTING_CHANGED	( WM_USER+12 )
#define HM_RELOADICONS			( WM_USER+13 )
#define HM_SETWINDOWTITLE		( WM_USER+14 )

#define TIMERID_UPDATING			1
#define TIMERID_RENAME				2

// flags for the PS structure
#define	PSF_CHANGED					0x00000100
#define	PSF_LOCKED					0x00000200
#define	PSF_INITIALIZED			0x00000400

#define	INIT_ICONS_NONE			0
#define	INIT_ICONS_OWNER		1
#define	INIT_ICONS_CONTACT	2
#define	INIT_ICONS_ALL			( INIT_ICONS_OWNER|INIT_ICONS_CONTACT )

/***********************************************************************************************************
 * internal variables
 ***********************************************************************************************************/

static BOOLEAN bInitIcons					= INIT_ICONS_NONE;
static HANDLE ghWindowList				= NULL;
static HANDLE ghDetailsInitEvent	= NULL;

static INT CALLBACK DlgProc( HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam );

CPsHdr::CPsHdr() :
   _ignore( 10, NULL )
{
  _dwSize = sizeof( *this );
  _hContact = NULL;
  _pszProto = NULL;
  _pszPrefix = NULL;
  _pPages = NULL;
  _numPages = 0;
  _dwFlags = 0;
  _hImages = NULL;
}

CPsHdr::~CPsHdr() {
	for( int i=0; i < _ignore.getCount(); i++)
		mir_free( _ignore[i]);
}

/***********************************************************************************************************
 * class CPsUpload
 ***********************************************************************************************************/

class CPsUpload {
public:
	enum EPsUpReturn {
		UPLOAD_CONTINUE = 0,
		UPLOAD_FINISH = 1,
		UPLOAD_FINISH_CLOSE = 2
	};

private:
	PROTOCOLDESCRIPTOR **_pPd;
	INT		_numProto;
	BOOLEAN	_bExitAfterUploading;
	HANDLE	_hUploading;
	LPPS	_pPs;
	
	/**
	 * name:	Upload
	 * class:	CPsUpload
	 * desc:	start upload process for the current protocol
	 * param:	none
	 * return:	0 on success, 1 otherwise
	 **/
	INT Upload()
	{
		CPsTreeItem *pti;

		// check if icq is online
		if( !IsProtoOnline( ( *_pPd )->szName ) ) {
			TCHAR		szMsg[MAX_PATH];
			LPTSTR	ptszProto;

			ptszProto = mir_a2t( ( *_pPd )->szName );
			mir_snprintfT( szMsg, SIZEOF( szMsg ), TranslateT( "Protocol '%s' is offline" ), ptszProto );
			mir_free( ptszProto );

			MsgBox( _pPs->hDlg, MB_ICON_WARNING, TranslateT( "Upload Details" ), szMsg,
				TranslateT( "You are not currently connected to the ICQ network.\nYou must be online in order to update your information on the server.\n\nYour changes will be saved to database only.") );
		}
		// start uploading process
		else {
			_hUploading = ( HANDLE )CallProtoService( ( *_pPd )->szName, PS_CHANGEINFOEX, CIXT_FULL, NULL );
			if( _hUploading && _hUploading != ( HANDLE )CALLSERVICE_NOTFOUND ) {
				EnableWindow( _pPs->pTree->Window(), FALSE );
				if( pti = _pPs->pTree->CurrentItem() ) {
					EnableWindow( pti->Wnd(), FALSE );
				}
				EnableWindow( GetDlgItem( _pPs->hDlg, IDOK ), FALSE );
				EnableWindow( GetDlgItem( _pPs->hDlg, IDAPPLY ), FALSE );
				mir_snprintfA( _pPs->szUpdating, SIZEOF( _pPs->szUpdating ), "%s ( %s )", Translate( "Uploading" ), ( *_pPd )->szName );
				ShowWindow( GetDlgItem( _pPs->hDlg, TXT_UPDATING ), SW_SHOW );
				SetTimer( _pPs->hDlg, TIMERID_UPDATING, 100, NULL );
				return 0;
			}
		}
		return 1;
	}

public:
	/**
	 * name:	CPsUpload
	 * class:	CPsUpload
	 * desc:	retrieves the list of installed protocols and initializes the class
	 * param:	pPs			- the owning propertysheet
	 *			bExitAfter	- whether the dialog is to close after upload or not
	 * return:	nothing
	 **/
	CPsUpload( LPPS pPs, BOOLEAN bExitAfter )
	{
		_pPs = pPs;
		_pPd = NULL;
		_numProto = 0;
		_hUploading = NULL;
		_bExitAfterUploading = bExitAfter;
	}

	INT UploadFirst() {
		// create a list of all protocols which support uploading contact information
		if( CallService( MS_PROTO_ENUMPROTOCOLS, ( WPARAM )&_numProto, ( LPARAM )&_pPd ) ) {
			return _bExitAfterUploading ? UPLOAD_FINISH_CLOSE : UPLOAD_FINISH;
		}
		return UploadNext();
	}

	/**
	 * name:	~CPsUpload
	 * class:	CPsUpload
	 * desc:	clear pointer to the upload object
	 * param:	none
	 * return:	nothing
	 **/
	~CPsUpload()
		{ _pPs->pUpload = NULL; }

	/**
	 * name:	Handle
	 * class:	CPsUpload
	 * desc:	returns the handle of the current upload process
	 * param:	none
	 * return:	handle of the current upload process
	 **/
	__inline HANDLE Handle() const
		{ return _hUploading; };

	/**
	 * name:	UploadNext
	 * class:	CPsUpload
	 * desc:	Search the next protocol which supports uploading contact information
	 *			and start uploading. Delete the object if ready
	 * param:	none
	 * return:	nothing
	 **/
	INT	UploadNext()
	{
		CHAR str[MAXMODULELABELLENGTH];
		while( _pPd && *_pPd && _numProto-- > 0 ) {
			if( ( *_pPd )->type == PROTOTYPE_PROTOCOL ) {
				mir_strncpy( str, ( *_pPd )->szName, MAXMODULELABELLENGTH );
				mir_strncat( str, PS_CHANGEINFOEX, MAXMODULELABELLENGTH );
				if( ServiceExists( str ) && !Upload() ) {
					_pPd++;
					return UPLOAD_CONTINUE;
				}
			}
			_pPd++;
		}
		return _bExitAfterUploading ? UPLOAD_FINISH_CLOSE : UPLOAD_FINISH;
	}
};

/***********************************************************************************************************
 * propertysheet
 ***********************************************************************************************************/

/**
 * name:	SortProc()
 * desc:	used for sorting the tab pages
 *
 * return:	-1 or 0 or 1
 **/
static INT SortProc( CPsTreeItem** item1, CPsTreeItem** item2 )
{
	if( *item1 && *item2 ) {
		if( ( *item2 )->Pos() > ( *item1 )->Pos() ) return -1;
		if( ( *item2 )->Pos() < ( *item1 )->Pos() ) return 1;
	}
	return 0;
}

/**
 * name:	ShowDialog()
 * desc:	this creates the DetailsDialog
 * param:	wParam - handle to contact
 *			lParam - not used
 *
 * return:	0 on success
 **/
static INT ShowDialog( WPARAM wParam, LPARAM lParam )
{
	HWND hWnd;

	// allow only one dialog per user
	if( hWnd = WindowList_Find( ghWindowList, ( HANDLE )wParam ) ) {
		SetForegroundWindow( hWnd );
		SetFocus( hWnd );
	}
	else {
		CPsHdr psh;
		POINT metrics;
		BOOLEAN bScanMetaSubContacts = FALSE;
		HICON hDefIcon;

		// init the treeview options
		if( DB::Setting::GetByte( SET_PROPSHEET_SORTITEMS, FALSE ) )
			psh._dwFlags |= PSTVF_SORTTREE;
		if( DB::Setting::GetByte( SET_PROPSHEET_GROUPS, TRUE ) )
			psh._dwFlags |= PSTVF_GROUPS;
		// create imagelist
		metrics.x = GetSystemMetrics( SM_CXSMICON );
		metrics.y = GetSystemMetrics( SM_CYSMICON );
		if( ( psh._hImages = ImageList_Create( metrics.x, metrics.y, ( IsWinVerXPPlus() ? ILC_COLOR32 : ILC_COLOR16 ) | ILC_MASK, 0, 1 ) ) == NULL ) {
			MsgErr( NULL, LPGENT( "Creating the imagelist failed!") );
			return 1;
		}

		hDefIcon = NIcoLib::GetIcon( ICO_TREE_DEFAULT );
		if( !hDefIcon )
		{
			hDefIcon = ( HICON ) LoadImage( ghInst, MAKEINTRESOURCE( IDI_DEFAULT ), IMAGE_ICON, metrics.x, metrics.y, 0 );
		}
		// add the default icon to imagelist
		ImageList_AddIcon( psh._hImages, hDefIcon );

		// init contact
		psh._hContact = ( HANDLE )wParam;
		if( psh._hContact == NULL ) {
			// mark owner icons as initiated
			bInitIcons |= INIT_ICONS_OWNER;
			psh._pszProto = NULL;
			psh._pszPrefix = NULL;
		}
		else {
			// get contact's protocol
			psh._pszPrefix = psh._pszProto = DB::Contact::Proto( ( HANDLE )wParam );
			if( psh._pszProto == NULL ) {
				MsgErr( NULL, LPGENT( "Could not find contact's protocol. Maybe it is not active!") );
				return 1;
			}
			// prepare scanning for metacontact's subcontact's pages
			if( bScanMetaSubContacts = DB::Module::IsMetaAndScan( psh._pszProto ) )
				psh._dwFlags |= PSF_PROTOPAGESONLY_INIT;
		}
		// add the pages
		NotifyEventHooks( ghDetailsInitEvent, ( WPARAM )&psh, wParam );
		if( !psh._pPages || !psh._numPages ) {
			MsgErr( NULL, LPGENT( "No pages have been added. Canceling dialog creation!") );
			return 1;
		}
		// metacontacts sub pages
		if( bScanMetaSubContacts )
		{
			INT numSubs = DB::MetaContact::SubCount( ( HANDLE )wParam );

			psh._dwFlags |= PSF_PROTOPAGESONLY;
			for( INT i = 0; i < numSubs; i++ ) 
			{
				psh._hContact = DB::MetaContact::Sub( ( HANDLE )wParam, i );
				if( psh._hContact ) 
				{
					psh._pszProto = DB::Contact::Proto( psh._hContact );
					if( ( INT )psh._pszProto != CALLSERVICE_NOTFOUND )
						NotifyEventHooks( ghDetailsInitEvent, ( WPARAM )&psh, ( LPARAM )psh._hContact );
				}
			}
			psh._hContact = ( HANDLE )wParam;
		}

		// sort the pages by the position read from database
		if( !( psh._dwFlags & PSTVF_SORTTREE ) ) {
			 qsort( psh._pPages, psh._numPages, sizeof( CPsTreeItem* ), 
				( INT ( * )( const VOID*, const VOID* ) )SortProc );
		}
		// create the dialog itself
		hWnd = CreateDialogParam( ghInst, MAKEINTRESOURCE( IDD_DETAILS ), NULL, DlgProc, ( LPARAM )&psh );
		if( !hWnd ) {
			MsgErr( NULL, LPGENT( "Details dialog failed to be created. Returning error is %d." ), GetLastError() );
		}
		else
			MagneticWindows_AddWindow(hWnd);
	}
	return 0;
}

/**
 * name:	AddPage()
 * desc:	this adds a new pages
 * param:	wParam	- The List of pages we want to add the new one to
 *			lParam	- it's the page to add
 *
 * return:	0
 **/
static INT AddPage( WPARAM wParam, LPARAM lParam )
{
	CPsHdr	*pPsh						= ( CPsHdr* )wParam;
	OPTIONSDIALOGPAGE *odp	= ( OPTIONSDIALOGPAGE* )lParam;

	// check size of the handled structures
	if( pPsh == NULL || odp == NULL || pPsh->_dwSize != sizeof( CPsHdr ) )
	{
		return 1;
	}
	if( odp->cbSize != sizeof( OPTIONSDIALOGPAGE ) &&
      odp->cbSize != OPTIONPAGE_OLD_SIZE2 &&
      odp->cbSize != OPTIONPAGE_OLD_SIZE3 ) 
	{
		MsgErr( NULL, LPGENT( "The Page to add has invalid size %d bytes!" ), odp->cbSize );
		return 1;
	}

	// try to check whether the flag member is initialized or not
	odp->flags = odp->flags > ( ODPF_UNICODE|ODPF_BOLDGROUPS|ODPF_ICON|PSPF_PROTOPREPENDED ) ? 0 : odp->flags;


	BOOLEAN bIsUnicode = ( odp->flags & ODPF_UNICODE ) == ODPF_UNICODE;
	TCHAR* ptszTitle = NULL;
	if( !bIsUnicode )
		ptszTitle = mir_a2t( odp->pszTitle);
	else
		ptszTitle = mir_tstrdup( odp->ptszTitle );

	// handle to ignore doubled pages
	if( pPsh->_dwFlags & PSF_PROTOPAGESONLY ) {
		for( int i = 0; i < pPsh->_ignore.getCount(); i++ ) {
			if( !lstrcmp( pPsh->_ignore[i], ptszTitle ) ) {
				if( ptszTitle ) {
					mir_free(ptszTitle);
				}
				return 0;
			}
		}
	}
	else if( pPsh->_dwFlags & PSF_PROTOPAGESONLY_INIT ) {
		pPsh->_ignore.insert( ptszTitle );
	}
	else if( ptszTitle ) {
		mir_free(ptszTitle);
	}

	// resize the array
	pPsh->_pPages = ( CPsTreeItem** )realloc( pPsh->_pPages, ( pPsh->_numPages + 1 ) * sizeof( CPsTreeItem* ) );
	if( pPsh->_pPages == NULL ) {
		pPsh->_numPages = 0;
		return 1;
	}
	// create the new tree item
	try {
		CPsTreeItem *pNew = new CPsTreeItem();		
		
		if( pNew ) {
			if( !pNew->Create( pPsh, odp ) ) {
				pPsh->_pPages[pPsh->_numPages++] = pNew;
				return 0;
			}
			delete pNew;
		}
		pPsh->_pPages[pPsh->_numPages] = NULL;
	}
	catch( ... ) {
		MsgErr( NULL, LPGENT( "Fatal memory allocation error on creating treeitem object") );
		pPsh->_pPages[pPsh->_numPages] = NULL;
	}
	return 1;
}

/**
 * name:	OnDeleteContact()
 * desc:	a user was deleted, so need to close its details dialog, if one open
 *
 * return:	0
 **/
static INT OnDeleteContact( WPARAM wParam, LPARAM lParam )
{
   HWND hWnd = WindowList_Find( ghWindowList, ( HANDLE )wParam );
   if( hWnd != NULL )
	   DestroyWindow( hWnd );
   return 0;
}

/**
 * name:	OnShutdown()
 * desc:	we need to emptify the windowlist
 *
 * return:	0
 **/
static INT OnShutdown( WPARAM wParam, LPARAM lParam )
{
   WindowList_BroadcastAsync( ghWindowList, WM_DESTROY, 0, 0 );
   return 0;
}

/**
 * name:	AddProtocolPages()
 * desc:	is called by Miranda if user selects to display the userinfo dialog
 * param:	odp		 - optiondialogpage structure to use
 *			wParam   - the propertysheet init structure to pass
 *			pszProto - the protocol name to prepend as item name ( can be NULL )
 * return:	0
 **/
static INT AddProtocolPages( OPTIONSDIALOGPAGE& odp, WPARAM wParam, LPSTR pszProto = NULL )
{
	CHAR szTitle[MAX_PATH];
	const BYTE ofs = ( pszProto ) ? mir_snprintfA( szTitle, SIZEOF( szTitle ), "%s\\", pszProto ) : 0;

	odp.pszTitle = szTitle;
	
	odp.pszTemplate   = MAKEINTRESOURCEA( IDD_CONTACT_GENERAL );
	odp.position      = -2110000000;
	odp.pfnDlgProc    = DlgProc_pspGeneral;
	odp.hIcon         =	( HICON )ICONINDEX( IDI_TREE_GENERAL );
	mir_strncpy( szTitle + ofs, "General", SIZEOF( szTitle ) - ofs );
	AddPage( wParam, ( LPARAM )&odp );

	odp.pszTemplate   = MAKEINTRESOURCEA( IDD_CONTACT_ADDRESS );
	odp.position      = -2100000000;
	odp.pfnDlgProc    = DlgProc_pspContactHome;
	odp.hIcon         = ( HICON )ICONINDEX( IDI_TREE_ADDRESS );
	mir_strncpy( szTitle + ofs, "General\\Contact (private)", SIZEOF( szTitle ) - ofs );
	AddPage( wParam, ( LPARAM )&odp );

	odp.pszTemplate   = MAKEINTRESOURCEA( IDD_CONTACT_ORIGIN );
	odp.position      = -2090000000;
	odp.pfnDlgProc    = DlgProc_pspOrigin;
	odp.hIcon         = ( HICON )ICONINDEX( IDI_TREE_ADVANCED );
	mir_strncpy( szTitle + ofs, "General\\Origin", SIZEOF( szTitle ) - ofs );
	AddPage( wParam, ( LPARAM )&odp );
    
	odp.pszTemplate   = MAKEINTRESOURCEA( IDD_CONTACT_ANNIVERSARY );
	odp.position      = -2080000000;
	odp.pfnDlgProc    = DlgProc_pspAnniversary;
	odp.hIcon         = ( HICON )ICONINDEX( IDI_BIRTHDAY );
	mir_strncpy( szTitle + ofs, "General\\Anniversaries", SIZEOF( szTitle ) - ofs );
	AddPage( wParam, ( LPARAM )&odp );

	odp.pszTemplate   = MAKEINTRESOURCEA( IDD_CONTACT_COMPANY );
	odp.position      = -2070000000;
	odp.pfnDlgProc    = DlgProc_pspCompany;
	odp.hIcon         = ( HICON )ICONINDEX( IDI_TREE_COMPANY );
	mir_strncpy( szTitle + ofs, "Work", SIZEOF( szTitle ) - ofs );
	AddPage( wParam, ( LPARAM )&odp );

	odp.pszTemplate   = MAKEINTRESOURCEA( IDD_CONTACT_ADDRESS );
	odp.position      = -2060000000;
	odp.pfnDlgProc    = DlgProc_pspContactWork;
	odp.hIcon         = ( HICON )ICONINDEX( IDI_TREE_ADDRESS );
	mir_strncpy( szTitle + ofs, "Work\\Contact (Work)", SIZEOF( szTitle ) - ofs );
	AddPage( wParam, ( LPARAM )&odp );
	  
	odp.pszTemplate   = MAKEINTRESOURCEA( IDD_CONTACT_ABOUT );
	odp.position      = -2050000000;
	odp.pfnDlgProc    = DlgProc_pspAbout;
	odp.hIcon         = ( HICON )ICONINDEX( IDI_TREE_ABOUT );
	mir_strncpy( szTitle + ofs, "About", SIZEOF( szTitle ) - ofs );
	AddPage( wParam, ( LPARAM )&odp );

	odp.pszTemplate   = MAKEINTRESOURCEA( IDD_CONTACT_PROFILE );
	odp.position      = -2030000000;
	odp.pfnDlgProc    = DlgProc_pspContactProfile;
	odp.hIcon         = ( HICON )ICONINDEX( IDI_TREE_PROFILE );
	mir_strncpy( szTitle + ofs, "About\\Profile", SIZEOF( szTitle ) - ofs );
	AddPage( wParam, ( LPARAM )&odp );
	return 0;
}

/**
 * name:	InitDetails
 * desc:	is called by Miranda if user selects to display the userinfo dialog
 * param:	wParam   - the propertysheet init structure to pass
 *			lParam   - handle to contact whose information are read
 * return:	0
 **/
static INT InitDetails( WPARAM wParam, LPARAM lParam )
{
	CPsHdr* pPsh = ( CPsHdr* )wParam;

	if( !( pPsh->_dwFlags & PSF_PROTOPAGESONLY ) ) {

		OPTIONSDIALOGPAGE odp;
		BOOLEAN bChangeDetailsEnabled = myGlobals.CanChangeDetails && DB::Setting::GetByte( SET_PROPSHEET_CHANGEMYDETAILS, FALSE );
		
		// important to avoid craches!!
		ZeroMemory( &odp, sizeof( odp ) );

		if( lParam || bChangeDetailsEnabled ) {
			odp.cbSize        = sizeof( odp );
			odp.hInstance     = ghInst;
			odp.flags         = ODPF_BOLDGROUPS|ODPF_ICON;
			odp.ptszGroup			= NIcoLib::GetDefaultIconFileName();

			if( lParam ) {

				// ignore common pages for weather contacts
				if( !pPsh->_pszProto || stricmp( pPsh->_pszProto, "weather") ) {
					AddProtocolPages( odp, wParam );
					odp.pszTitle  = "About\\Notes";
				}
				else {
					odp.pszTitle  = "Notes";
				}
				odp.pszTemplate   = MAKEINTRESOURCEA( IDD_CONTACT_ABOUT );
				odp.position      = -2040000000;
				odp.pfnDlgProc    = DlgProc_pspMyNotes;
				odp.hIcon         = ( HICON )ICONINDEX( IDI_TREE_NOTES );
				AddPage( wParam, ( LPARAM )&odp );
			}
			else 
			if( !( pPsh->_dwFlags & PSTVF_INITICONS ) ) {
				PROTOCOLDESCRIPTOR **pd;
				INT ProtoCount, i;
				CHAR str[MAXMODULELABELLENGTH];

				odp.flags |= PSPF_PROTOPREPENDED;

				// create a list of all protocols which support uploading contact information
				if( !CallService( MS_PROTO_ENUMPROTOCOLS, ( WPARAM )&ProtoCount, ( LPARAM )&pd ) ) {
					for( i = 0; i < ProtoCount; i++ ) {
						if( pd[i]->type == PROTOTYPE_PROTOCOL ) {
							pPsh->_pszProto = pd[i]->szName;
							mir_snprintfA( str, MAXMODULELABELLENGTH, "%s"PS_CHANGEINFOEX, pd[i]->szName );
							if( ServiceExists( str ) ) AddProtocolPages( odp, wParam, pd[i]->szName );
						}
					}
				}
			}
		}
	}
	return 0;
}

/**
 * name:	InitTreeIcons()
 * desc:	initalize all treeview icons
 * param:	none
 *
 * return:	nothing
 **/
VOID InitTreeIcons()
{
	// make sure this is run only once
	if( !( bInitIcons & INIT_ICONS_ALL ) ) {
		CPsHdr psh;
		POINT metrics = {0};
		INT i = 0;

		psh._dwFlags = PSTVF_INITICONS;

		metrics.x = GetSystemMetrics( SM_CXSMICON );
		metrics.y = GetSystemMetrics( SM_CYSMICON );
		if( psh._hImages = ImageList_Create( metrics.x, metrics.y, ( IsWinVerXPPlus() ? ILC_COLOR32 : ILC_COLOR16 ) | ILC_MASK, 0, 1 ) ) {
			HICON hDefIcon = NIcoLib::GetIcon( ICO_TREE_DEFAULT );
			if( !hDefIcon )
			{
				hDefIcon = ( HICON ) LoadImage( ghInst, MAKEINTRESOURCE( IDI_DEFAULT ), IMAGE_ICON, metrics.x, metrics.y, 0 );
			}
			// add the default icon to imagelist
			ImageList_AddIcon( psh._hImages, hDefIcon );
		}

		// avoid pages from loading doubled
		if( !( bInitIcons & INIT_ICONS_CONTACT ) ) {
			LPCSTR pszContactProto = NULL;
			PROTOCOLDESCRIPTOR **pd;
			INT ProtoCount = 0;

			psh._dwFlags |= PSF_PROTOPAGESONLY_INIT;
			
			// enumerate all protocols
			if( !CallService( MS_PROTO_ENUMPROTOCOLS, ( WPARAM )&ProtoCount, ( LPARAM )&pd ) ) {
				for( i = 0; i < ProtoCount; i++ ) {
					if( pd[i]->type == PROTOTYPE_PROTOCOL ) {
						// enumerate all contacts
						for( psh._hContact = DB::Contact::FindFirst();
							psh._hContact != NULL;
							psh._hContact = DB::Contact::FindNext( psh._hContact ) )
						{
							// compare contact's protocol to the current one, to add
							pszContactProto = DB::Contact::Proto( psh._hContact );
							if( ( INT )pszContactProto != CALLSERVICE_NOTFOUND && !mir_strcmp( pd[i]->szName, pszContactProto ) ) {
								// call a notification for the contact to retrieve all protocol specific tree items
								NotifyEventHooks( ghDetailsInitEvent, ( WPARAM )&psh, ( LPARAM )psh._hContact );
								if( psh._pPages ) {
									for( INT i = 0; i < psh._numPages; i++ )
										if( psh._pPages[i] ) delete psh._pPages[i];
									psh._numPages = 0;
									FREE( psh._pPages );
									psh._dwFlags = PSTVF_INITICONS|PSF_PROTOPAGESONLY;
								}
								break;
							}
						}
					}
				}
			}
			bInitIcons |= INIT_ICONS_CONTACT;
		}
		// load all treeitems for owner contact
		if( !( bInitIcons & INIT_ICONS_OWNER ) ) {
			psh._hContact = NULL;
			psh._pszProto = NULL;
			NotifyEventHooks( ghDetailsInitEvent, ( WPARAM )&psh, ( LPARAM )psh._hContact );
			if( psh._pPages ) {
				for( i = 0; i < psh._numPages; i++ )
					if( psh._pPages[i] ) delete psh._pPages[i];
				psh._numPages = 0;
				FREE( psh._pPages );
			}
			bInitIcons |= INIT_ICONS_OWNER;
		}
		ImageList_Destroy( psh._hImages );
	}
}

/**
 * name:	UnLoadModule()
 * desc:	unload the UserInfo Module
 *
 * return:	nothing
 **/
VOID UnLoadModule()
{
	DestroyHookableEvent( ghDetailsInitEvent );
}

/**
 * name:	LoadModule()
 * desc:	load the UserInfo Module
 *
 * return:	nothing
 **/
VOID LoadModule()
{
	ghDetailsInitEvent = CreateHookableEvent( ME_USERINFO_INITIALISE );

	CreateServiceFunction( MS_USERINFO_SHOWDIALOG, ShowDialog );
	CreateServiceFunction( MS_USERINFO_ADDPAGE, AddPage );

	HookEvent( ME_DB_CONTACT_DELETED, OnDeleteContact );
	HookEvent( ME_SYSTEM_PRESHUTDOWN, OnShutdown );
	HookEvent( ME_USERINFO_INITIALISE, InitDetails );
	ghWindowList = ( HANDLE )CallService( MS_UTILS_ALLOCWINDOWLIST, 0, 0 );

	// check whether changing my details via UserInfoEx is basically possible
	{
		PROTOCOLDESCRIPTOR **pd;
		INT ProtoCount, i;
		CHAR str[MAXMODULELABELLENGTH];
		
		myGlobals.CanChangeDetails = FALSE;
		if( !CallService( MS_PROTO_ENUMPROTOCOLS, ( WPARAM )&ProtoCount, ( LPARAM )&pd ) ) {
			for( i = 0; i < ProtoCount; i++ ) {
				if( pd[i]->type == PROTOTYPE_PROTOCOL ) {
					mir_snprintfA( str, MAXMODULELABELLENGTH, "%s"PS_CHANGEINFOEX, pd[i]->szName );
					if( ServiceExists( str ) ){
						myGlobals.CanChangeDetails = TRUE;
						break;
					}
				}
			}
		}
	}
}

static void ResetUpdateInfo( LPPS pPs )
{
	INT i;

	// free the array of accomblished acks
	for( i = 0; i < ( INT )pPs->nSubContacts; i++ )
		MIR_FREE( pPs->infosUpdated[i].acks );
	MIR_FREE( pPs->infosUpdated );
	pPs->nSubContacts = 0;
}

/*
============================================================================================
	PropertySheet's Dialog Procedures
============================================================================================
*/

/**
 * name:	DlgProc()
 * desc:	dialog procedure for the main propertysheet dialog box
 *
 * return:	0 or 1
 **/
static INT_PTR CALLBACK DlgProc( HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
	LPPS pPs = ( LPPS )GetUserData( hDlg );
	   
	// do not process any message if pPs is no longer existent
	if( !PtrIsValid( pPs ) && uMsg != WM_INITDIALOG )
		return FALSE;

	switch ( uMsg )
	{
		/**
		 * name:	WM_INITDIALOG
		 * desc:	initiates all dialog controls
		 * param:	wParam - not used
		 *				lParam - pointer to a PSHDR structure, which contains all information to create the dialog
		 *
		 * return:	TRUE if everything is ok, FALSE if dialog creation should fail
		 **/
		case WM_INITDIALOG:
		{
			CPsHdr*	pPsh = ( CPsHdr* )lParam;
			WORD	needWidth = 0;
			RECT	rc;
			
			if( !pPsh || pPsh->_dwSize != sizeof( CPsHdr ) )
				return FALSE;

			TranslateDialogDefault( hDlg );

			//
			// create data structures
			//
			if( !( pPs = ( LPPS )malloc( sizeof( PS ) ) ) )
				return FALSE;
			ZeroMemory( pPs, sizeof( PS ) );

			if( !( pPs->pTree = new CPsTree( pPs ) ) )
				return FALSE;
			if( !( pPs->pTree->Create( GetDlgItem( hDlg, STATIC_TREE ), pPsh ) ) ) {
				return FALSE;
			}
			SetUserData( hDlg, pPs );
			pPs->hDlg = hDlg;
			pPs->dwFlags |= PSF_LOCKED;
			pPs->hContact = pPsh->_hContact;
			pPs->hProtoAckEvent = HookEventMessage( ME_PROTO_ACK, hDlg, HM_PROTOACK );
			pPs->hSettingChanged = HookEventMessage( ME_DB_CONTACT_SETTINGCHANGED, hDlg, HM_SETTING_CHANGED );
			pPs->hIconsChanged = HookEventMessage( ME_SKIN2_ICONSCHANGED, hDlg, HM_RELOADICONS );

			//
			// set icons
			//
			SendMessage( hDlg, WM_SETICON, ICON_BIG, ( LPARAM )LoadIcon( ghInst, MAKEINTRESOURCE( IDI_MAIN ) ) );
			DlgProc( hDlg, HM_RELOADICONS, NULL, NULL );

			//
			// load basic protocol for current contact ( for faster load later on and better handling for owner protocol )
			//
			if( pPs->hContact ) mir_strncpy( pPs->szBaseProto, pPsh->_pszPrefix, MAXMODULELABELLENGTH );

			// set the windowtitle
			DlgProc( hDlg, HM_SETWINDOWTITLE, NULL, NULL );
			
			// translate Userinfo buttons
			{
				SendDlgItemMessage( hDlg, BTN_UPDATE, BUTTONTRANSLATE, NULL, NULL );
				SendDlgItemMessage( hDlg, IDOK, BUTTONTRANSLATE, NULL, NULL );
				SendDlgItemMessage( hDlg, IDCANCEL, BUTTONTRANSLATE, NULL, NULL );
				SendDlgItemMessage( hDlg, IDAPPLY, BUTTONTRANSLATE, NULL, NULL );
				SendDlgItemMessage( hDlg, BTN_EXPORT, BUTTONADDTOOLTIP, ( WPARAM )TranslateT( "Export to file" ), MBF_TCHAR );
				SendDlgItemMessage( hDlg, BTN_IMPORT, BUTTONADDTOOLTIP, ( WPARAM )TranslateT( "Import from file" ), MBF_TCHAR );
			}

			//
			// set bold font for name in description area
			//
			{
				LOGFONT lf;
				HFONT hNormalFont = ( HFONT )SendDlgItemMessage( hDlg, TXT_NAME, WM_GETFONT, 0, 0 );

				GetObject( hNormalFont, sizeof( lf ), &lf );
				lf.lfWeight = FW_BOLD;
				pPs->hBoldFont = CreateFontIndirect( &lf );
				SendDlgItemMessage( hDlg, TXT_NAME, WM_SETFONT, ( WPARAM )pPs->hBoldFont, 0 );
			}

			//
			// initialize the optionpages and tree control
			//
			if( !pPs->pTree->InitTreeItems( ( LPWORD )&needWidth ) )
				return FALSE;

			//
			// move and resize dialog and its controls
			//
			{
				HWND hCtrl;
				RECT rcTree;
				POINT pt = { 0, 0 };
				INT addWidth = 0;

				// at least add width of scrollbar
				needWidth += 8 + GetSystemMetrics( SM_CXVSCROLL );

				// get tree rectangle
				GetWindowRect( hDlg, &pPs->rcDisplay );
				GetWindowRect( pPs->pTree->Window(), &rcTree );
				ClientToScreen( hDlg, &pt );
				OffsetRect( &rcTree, -pt.x, -pt.y );

				// calculate the amout of pixels to resize the dialog by?
				if( needWidth > rcTree.right - rcTree.left ) {
					RECT rcMax = { 0, 0, 0, 0 };

					rcMax.right = 280;
					MapDialogRect( hDlg, &rcMax );
					addWidth = min( needWidth, rcMax.right ) - rcTree.right + rcTree.left;
					rcTree.right += addWidth;
					// resize tree
					MoveWindow( pPs->pTree->Window(), rcTree.left, rcTree.top, rcTree.right - rcTree.left, rcTree.bottom - rcTree.top, FALSE );

					pPs->rcDisplay.right += addWidth;
					MoveWindow( hDlg, pPs->rcDisplay.left, pPs->rcDisplay.top, 
						pPs->rcDisplay.right - pPs->rcDisplay.left, 
						pPs->rcDisplay.bottom - pPs->rcDisplay.top, FALSE );

				}
				// calculate dislpay area for pages
				OffsetRect( &pPs->rcDisplay, -pt.x, -pt.y );
				pPs->rcDisplay.bottom = rcTree.bottom;
				pPs->rcDisplay.left = rcTree.right + 4;
				pPs->rcDisplay.top = rcTree.top;
				pPs->rcDisplay.right -= 4;

				// move and resize the rest of the controls
				if( addWidth > 0 ) {
					const WORD	idResize[] = { STATIC_WHITERECT, STATIC_LINE1, STATIC_LINE2, TXT_NAME };
					const WORD	idMove[] = { ICO_DLGLOGO, IDOK, IDCANCEL, IDAPPLY };
					WORD i;

					for( i = 0; i < SIZEOF( idResize ); i++ ) {
						if( hCtrl = GetDlgItem( hDlg, idResize[i] ) ) {
							GetWindowRect( hCtrl, &rc );
							OffsetRect( &rc, -pt.x, -pt.y );
							MoveWindow( hCtrl, rc.left, rc.top, rc.right - rc.left + addWidth, rc.bottom - rc.top, FALSE );
						}
					}						
					for( i = 0; i < SIZEOF( idMove ); i++ ) {
						if( hCtrl = GetDlgItem( hDlg, idMove[i] ) ) {
							GetWindowRect( hCtrl, &rc );
							OffsetRect( &rc, -pt.x, -pt.y );
							MoveWindow( hCtrl, rc.left + addWidth, rc.top, rc.right - rc.left, rc.bottom - rc.top, FALSE );
						}
					}						
				}
				// restore window position and add required size				
				Utils_RestoreWindowPositionNoSize( hDlg, NULL, MODNAME, "DetailsDlg" );
			}

			//
			// show the first propsheetpage
			//
			// finally add the dialog to the window list
			WindowList_Add( ghWindowList, hDlg, pPs->hContact );

			// show the dialog
			pPs->dwFlags &= ~PSF_LOCKED;
			pPs->dwFlags |= PSF_INITIALIZED;


			//
			// initialize the "updating" button and statustext and check for online status
			//
			pPs->updateAnimFrame = 0;
			if( pPs->hContact && *pPs->szBaseProto ) 
			{
				GetDlgItemTextA( hDlg, TXT_UPDATING, pPs->szUpdating, SIZEOF( pPs->szUpdating ) );
				ShowWindow( GetDlgItem( hDlg, TXT_UPDATING ), SW_HIDE );
				
				if( DlgProc( hDlg, M_CHECKONLINE, NULL, NULL ) ) 
				{
					DlgProc( hDlg, WM_COMMAND, MAKEWPARAM( BTN_UPDATE, BN_CLICKED ), ( LPARAM )GetDlgItem( hDlg, BTN_UPDATE ) );
				}
			}


			{
				INT nPage = pPs->pTree->CurrentItemIndex();
				if( !pPs->pTree->IsIndexValid( nPage ) )
					nPage = 0;
				TreeView_Select( pPs->pTree->Window(), NULL, TVGN_CARET );
				TreeView_Select( pPs->pTree->Window(), pPs->pTree->TreeItemHandle( nPage ), TVGN_CARET );
				ShowWindow( hDlg, SW_SHOW );
			}
			return TRUE;
		}

		/**
		 * name:	WM_TIMER
		 * desc:	is called to display the "updating" text in the status area
		 * param:	wParam - not used
		 *			lParam - not used
		 *
		 * return:	always FALSE
		 **/
		case WM_TIMER:
			switch( wParam ) {
				
				case TIMERID_RENAME:
					pPs->pTree->BeginLabelEdit( NULL );
					KillTimer( hDlg, TIMERID_RENAME );
					return FALSE;

				case TIMERID_UPDATING:
				{
					CHAR str[84];

					mir_snprintfA( str, SIZEOF( str ), "%.*s%s%.*s", pPs->updateAnimFrame%10, ".........", pPs->szUpdating, pPs->updateAnimFrame%10, "........." );
					SetDlgItemTextA( hDlg, TXT_UPDATING, str );
					if( ++pPs->updateAnimFrame == UPDATEANIMFRAMES )
						pPs->updateAnimFrame = 0;
					return FALSE;
				}
			}
			break;

		/**
		 * name:	WM_CTLCOLORSTATIC
		 * desc:	sets the colour of some of the dialog's static controls
		 * param:	wParam - HWND of the contrls
		 *			lParam - HDC for drawing
		 *
		 * return:	StockObject
		 **/
		case WM_CTLCOLORSTATIC:
			switch( GetWindowLong( ( HWND )lParam, GWL_ID ) ) {
				case ICO_DLGLOGO:
				case STATIC_WHITERECT:
					SetBkColor( ( HDC )wParam, RGB( 255, 255, 255 ) );
					return ( BOOL )GetStockObject( WHITE_BRUSH );
				case TXT_UPDATING:
				{
					COLORREF textCol, bgCol, newCol;
					INT ratio;

					textCol = GetSysColor( COLOR_BTNTEXT );
					bgCol = GetSysColor( COLOR_3DFACE );
					ratio = abs( UPDATEANIMFRAMES/2 - pPs->updateAnimFrame ) * 510 / UPDATEANIMFRAMES;
					newCol = RGB( GetRValue( bgCol ) + ( GetRValue( textCol ) - GetRValue( bgCol ) ) * ratio / 256,
							GetGValue( bgCol ) + ( GetGValue( textCol ) - GetGValue( bgCol ) ) * ratio / 256,
							GetBValue( bgCol ) + ( GetBValue( textCol ) - GetBValue( bgCol ) ) * ratio / 256 );
					SetTextColor( ( HDC )wParam, newCol );
					SetBkColor( ( HDC )wParam, GetSysColor( COLOR_3DFACE ) );
					return ( BOOL )GetSysColorBrush( COLOR_3DFACE );
				}
			}
			SetBkMode( ( HDC )wParam, TRANSPARENT );
			return ( BOOL )GetStockObject( NULL_BRUSH );

		/**
		 * name:	PSM_CHANGED
		 * desc:	indicates the propertysheet and the current selected page as changed
		 * param:	wParam - not used
		 *			lParam - not used
		 *
		 * return:	TRUE if successful FALSE if the dialog is locked at the moment
		 **/
		case PSM_CHANGED:
			if( !( pPs->dwFlags & PSF_LOCKED ) ) {   
				pPs->dwFlags |= PSF_CHANGED;
				pPs->pTree->CurrentItem()->AddFlags( PSPF_CHANGED );
				EnableWindow( GetDlgItem( hDlg, IDAPPLY ), TRUE );
				return TRUE;
			}
			break;

		/**
		 * name:	PSM_GETBOLDFONT
		 * desc:	returns the bold font
		 * param:	wParam - not used
		 *			lParam - pointer to a HFONT, which takes the boldfont
		 *
		 * return:	TRUE if successful, FALSE otherwise
		 **/
		case PSM_GETBOLDFONT:
			if( pPs->hBoldFont && lParam ) {
				*( HFONT* )lParam = pPs->hBoldFont;
				SetWindowLongPtr( hDlg, DWL_MSGRESULT, ( LONG_PTR )pPs->hBoldFont );
				return TRUE;
			}
			*( HFONT* )lParam = NULL;
			SetWindowLongPtr( hDlg, DWL_MSGRESULT, 0l );
			break;

		/**
		 * name:	PSM_GETCONTACT
		 * desc:	returns the handle to the contact, associated with this propertysheet
		 * param:	wParam - index or -1 for current item
		 *			lParam - pointer to a HANDLE, which takes the contact handle
		 *
		 * return:	TRUE if successful, FALSE otherwise
		 **/
		case PSM_GETCONTACT:
			if( lParam ) {
				CPsTreeItem *pti = ( ( INT )wParam != -1 ) 
					? pPs->pTree->TreeItem( ( INT )wParam )
					: pPs->pTree->CurrentItem();

				// prefer to return the contact accociated with the current page
				if( pti && pti->hContact() != INVALID_HANDLE_VALUE ) {
					*( HANDLE* )lParam = pti->hContact();
					SetWindowLongPtr( hDlg, DWL_MSGRESULT, ( LONG_PTR )pti->hContact() );
					return TRUE;
				}
				
				// return contact who owns the details dialog
				if( pPs->hContact != INVALID_HANDLE_VALUE ) {
					*( HANDLE* )lParam = pPs->hContact;
					SetWindowLongPtr( hDlg, DWL_MSGRESULT, ( LONG_PTR )pPs->hContact );
					return TRUE;
				}
				*( HANDLE* )lParam = NULL;
				SetWindowLongPtr( hDlg, DWL_MSGRESULT, NULL );
			}
			break;

		/**
		 * name:	PSM_GETBASEPROTO
		 * desc:	returns the basic protocol module for the associated contact
		 * param:	wParam - index or -1 for current item
		 *			lParam - pointer to a LPCSTR which takes the protocol string pointer
		 *
		 * return:	TRUE if successful, FALSE otherwise
		 **/
		case PSM_GETBASEPROTO:
			if( lParam ) {
				CPsTreeItem *pti = ( ( INT )wParam != -1 ) 
					? pPs->pTree->TreeItem( ( INT )wParam )
					: pPs->pTree->CurrentItem();
				
				if( pti && pti->Proto() ) {
					// return custom protocol for the current page
					*( LPCSTR* )lParam = pti->Proto();
					SetWindowLongPtr( hDlg, DWL_MSGRESULT, ( LONG_PTR )pti->Proto() );
					return TRUE;
				}

				if( *pPs->szBaseProto ) {
					// return global protocol
					*( LPSTR* )lParam = pPs->szBaseProto;
					SetWindowLongPtr( hDlg, DWL_MSGRESULT, ( LONG_PTR )pPs->szBaseProto );
					return TRUE;
				}
			}
			*( LPCSTR* )lParam = NULL;
			SetWindowLongPtr( hDlg, DWL_MSGRESULT, 0l );
			break;

		/**
		 * name:	PSM_ISLOCKED
		 * desc:	returns the lock state of the propertysheetpage
		 * param:	wParam - not used
		 *			lParam - not used
		 *
		 * return:	TRUE if propertysheet is locked, FALSE if not
		 **/
		case PSM_ISLOCKED:
		{
			BOOLEAN bLocked = ( pPs->dwFlags & PSF_LOCKED ) == PSF_LOCKED;

			SetWindowLongPtr( hDlg, DWL_MSGRESULT, bLocked );
			return bLocked;
		}

		/**
		 * name:	PSM_FORCECHANGED
		 * desc:	force all propertysheetpages to update their controls with new values from the database
		 * param:	wParam - whether to replace changed settings too or not
 		 *			lParam - not used
		 *
		 * return:	always FALSE
		 **/
		case PSM_FORCECHANGED:
			if( !( pPs->dwFlags & PSF_LOCKED ) ) { 
				BOOLEAN bChanged;

				pPs->dwFlags |= PSF_LOCKED;
				if( bChanged = pPs->pTree->OnInfoChanged() )
					pPs->dwFlags |= PSF_CHANGED;
				else
					pPs->dwFlags &= ~PSF_CHANGED;
				pPs->dwFlags &= ~PSF_LOCKED;
				EnableWindow( GetDlgItem( hDlg, IDAPPLY ), bChanged );
			}
			break;

		/**
		 * name:	PSM_DLGMESSAGE
		 * desc:	Sends a message to a specified propertysheetpage
		 * param:	wParam - not used
		 *			lParam - LPDLGCOMMAND structure, which contains information about the message to forward
		 *
		 * return:	E_FAIL if the page was not found
		 **/
		case PSM_DLGMESSAGE:
		{
			LPDLGCOMMAND pCmd = ( LPDLGCOMMAND )lParam;
			CPsTreeItem *pti;
			
			if( pCmd && ( pti = pPs->pTree->FindItemByResource( pCmd->hInst, pCmd->idDlg ) ) &&	pti->Wnd() ) {
				if( !pCmd->idDlgItem )
					return SendMessage( pti->Wnd(), pCmd->uMsg, pCmd->wParam, pCmd->lParam );
				else
					return SendDlgItemMessage( pti->Wnd(), pCmd->idDlgItem, pCmd->uMsg, pCmd->wParam, pCmd->lParam );
			}
			return E_FAIL;
		}

		/**
		 * name:	PSM_GETPAGEHWND
		 * desc:	get the window handle for a specified propertysheetpage
		 * param:	wParam - recource id of the dialog recource
		 *			lParam - hinstance of the plugin, which created the dialog box
		 *
		 * return:	TRUE if handle was found and dialog was created before, false otherwise
		 **/
		case PSM_GETPAGEHWND:
		{
			CPsTreeItem *pti;
			if( pti = pPs->pTree->FindItemByResource( ( HINSTANCE )lParam, wParam ) ) {
				SetWindowLongPtr( hDlg, DWL_MSGRESULT, ( LONG )pti->Wnd() );
				return ( pti->Wnd() != NULL );
			}
			return FALSE;
		}

		/**
		 * name:	HM_SETWINDOWTITLE
		 * desc:	set the window title and text of the infobar
		 * param:	wParam - not used
		 *			lParam - DBCONTACTWRITESETTING structure if called by HM_SETTING_CHANGED message handler
		 *
		 * return:	FALSE
		 **/
		case HM_SETWINDOWTITLE:
		{
			LPCTSTR pszName = NULL; 
			TCHAR	newTitle[MAX_PATH];
			RECT	rc;
			POINT	pt = { 0, 0 };
			HWND	hName = GetDlgItem( hDlg, TXT_NAME );
			DBCONTACTWRITESETTING* pdbcws = ( DBCONTACTWRITESETTING* )lParam;

			if( !pPs->hContact )
				pszName = TranslateT( "Owner" );
			else
			if( pdbcws && pdbcws->value.type == DBVT_TCHAR )
				pszName = pdbcws->value.ptszVal;
			else
				pszName = DB::Contact::DisplayName( pPs->hContact );

			SetWindowText( hName, pszName );
			mir_snprintfT( newTitle, MAX_PATH, _T( "%s - %s" ), pszName, TranslateT( "Edit Contact Information") );
			SetWindowText( hDlg, newTitle );

			// redraw the name control
			ScreenToClient( hDlg, &pt );
			GetWindowRect( hName, &rc );
			OffsetRect( &rc, pt.x, pt.y );
			InvalidateRect( hDlg, &rc, TRUE );
			break;
		}

		/**
		 * name:	HM_RELOADICONS
		 * desc:	handles the changed icon event from the icolib plugin and reloads all icons
		 * param:	wParam - not used
		 *			lParam - not used
		 *
		 * return:	FALSE
		 **/
		case HM_RELOADICONS:
		{
			HWND	hCtrl;
			HICON	hIcon;
			const NIcoLib::ICONCTRL idIcon[] = {
				{ ICO_DLG_DETAILS,	STM_SETIMAGE,	ICO_DLGLOGO	},
				{ ICO_BTN_UPDATE,	BM_SETIMAGE,	BTN_UPDATE	},
				{ ICO_BTN_OK,		BM_SETIMAGE,	IDOK		},
				{ ICO_BTN_CANCEL,	BM_SETIMAGE,	IDCANCEL	},
				{ ICO_BTN_APPLY,	BM_SETIMAGE,	IDAPPLY		}
			};
			
			const INT numIconsToSet = DB::Setting::GetByte( SET_ICONS_BUTTONS, 1 ) ? SIZEOF( idIcon ) : 1;
			
			NIcoLib::SetCtrlIcons( hDlg, idIcon, numIconsToSet );
			
			if( hCtrl = GetDlgItem( hDlg, BTN_IMPORT ) ) {
				hIcon = NIcoLib::GetIcon( ICO_BTN_IMPORT );
				SendMessage( hCtrl, BM_SETIMAGE, IMAGE_ICON, ( LPARAM )hIcon );
				SetWindowText( hCtrl, hIcon ? _T( "" ) : _T( "I" ) );
			}
			if( hCtrl = GetDlgItem( hDlg, BTN_EXPORT ) ) {
				hIcon = NIcoLib::GetIcon( ICO_BTN_EXPORT );
				SendMessage( hCtrl, BM_SETIMAGE, IMAGE_ICON, ( LPARAM )hIcon );
				SetWindowText( hCtrl, hIcon ? _T( "" ) : _T( "E" ) );
			}
			// update page icons
			if( PtrIsValid( pPs ) && ( pPs->dwFlags & PSF_INITIALIZED ) )
				pPs->pTree->OnIconsChanged();
			break;
		}

		/**
		 * name:	M_CHECKONLINE
		 * desc:	determines whether miranda is online or not
		 * param:	wParam - not used
		 *			lParam - not used
		 *
		 * return:	TRUE if online, FALSE if offline
		 **/
		case M_CHECKONLINE:
		{
			if( IsProtoOnline( pPs->szBaseProto ) )
			{
				EnableWindow( GetDlgItem( hDlg, BTN_UPDATE ), !IsWindowVisible( GetDlgItem( hDlg, TXT_UPDATING ) ) );
				return TRUE;
			}

			EnableWindow( GetDlgItem( hDlg, BTN_UPDATE ), FALSE );
			EnableWindow( GetDlgItem( hDlg, TXT_UPDATING ), FALSE );
			break;
		}

		/**
		 * name:	HM_PROTOACK
		 * desc:	handles all acks from the protocol plugin
		 * param:	wParam - not used
		 *			lParam - pointer to a ACKDATA structure
		 *
		 * return:	FALSE
		 **/
		case HM_PROTOACK:
		{
			ACKDATA *ack = ( ACKDATA* )lParam;
			INT i, iSubContact;
			CPsTreeItem *pti;

			if( !ack->hContact && ack->type == ACKTYPE_STATUS )
				return DlgProc( hDlg, M_CHECKONLINE, NULL, NULL );
			
			switch( ack->type ) {
			
				case ACKTYPE_SETINFO:
				{
					if( ack->hContact != pPs->hContact || !pPs->pUpload || pPs->pUpload->Handle() != ack->hProcess )
						break;
					if( ack->result == ACKRESULT_SUCCESS ) {
						ShowWindow( GetDlgItem( hDlg, TXT_UPDATING ), SW_HIDE );
						KillTimer( hDlg, TIMERID_UPDATING );
						// upload next protocols contact information
						switch( pPs->pUpload->UploadNext() ) {
							case CPsUpload::UPLOAD_FINISH_CLOSE:
								MIR_DELETE( pPs->pUpload );
								DestroyWindow( hDlg );
							case CPsUpload::UPLOAD_CONTINUE:
								return FALSE;
							case CPsUpload::UPLOAD_FINISH:
								MIR_DELETE( pPs->pUpload );
								break;
						}
						DlgProc( hDlg, M_CHECKONLINE, NULL, NULL );
						EnableWindow( pPs->pTree->Window(), TRUE );
						if( pti = pPs->pTree->CurrentItem() ) {
							EnableWindow( pti->Wnd(), TRUE );
						}
						EnableWindow( GetDlgItem( hDlg, IDOK ), TRUE );
						pPs->dwFlags &= ~PSF_LOCKED;
					}
					else 
					if( ack->result == ACKRESULT_FAILED )	{
						MsgBox( hDlg, MB_ICON_WARNING, 
							LPGENT( "Upload ICQ Details" ),
							LPGENT( "Upload failed" ),
							LPGENT( "Your details were not uploaded successfully.\nThey were written to database only.") );
						KillTimer( hDlg, TIMERID_UPDATING );
						ShowWindow( GetDlgItem( hDlg, TXT_UPDATING ), SW_HIDE );
						DlgProc( hDlg, M_CHECKONLINE, NULL, NULL );

						// upload next protocols contact information
						switch( pPs->pUpload->UploadNext() ) {
							case CPsUpload::UPLOAD_FINISH_CLOSE:
								MIR_DELETE( pPs->pUpload );
								DestroyWindow( hDlg );
							case CPsUpload::UPLOAD_CONTINUE:
								return 0;
							case CPsUpload::UPLOAD_FINISH:
								MIR_DELETE( pPs->pUpload );
								break;
						}
						if( pti = pPs->pTree->CurrentItem() ) {
							EnableWindow( pti->Wnd(), TRUE );
						}
						// activate all controls again
						EnableWindow( pPs->pTree->Window(), TRUE );
						EnableWindow( GetDlgItem( hDlg, IDOK ), TRUE );
						pPs->dwFlags &= ~PSF_LOCKED;
					}
					break;
				}

				case ACKTYPE_GETINFO:

					// is contact the owner of the dialog or any metasubcontact of the owner? skip handling otherwise!
					if( ack->hContact != pPs->hContact ) {
						
						if( !myGlobals.szMetaProto )
							break;
						
						if( !DB::Setting::GetByte( SET_META_SCAN, TRUE ) )
							break;
						
						for( i = 0; i < pPs->nSubContacts; i++ ) {
							if( pPs->infosUpdated[i].hContact == ack->hContact ) {
								iSubContact = i;
								break;
							}
						}
						if( i == pPs->nSubContacts )
							break;
					}
					else {
						iSubContact = 0;
					}

					// if they're not gonna send any more ACK's don't let that mean we should crash
					if( !pPs->infosUpdated || ( !ack->hProcess && !ack->lParam ) ) {
						ResetUpdateInfo( pPs );

						ShowWindow( GetDlgItem( hDlg, TXT_UPDATING ), SW_HIDE );
						KillTimer( hDlg, TIMERID_UPDATING );
						DlgProc( hDlg, M_CHECKONLINE, NULL, NULL );
						break;                     
					}

					if( iSubContact < pPs->nSubContacts ) {

						// init the acks structure for a sub contact
						if( pPs->infosUpdated[iSubContact].acks == NULL ) {
							pPs->infosUpdated[iSubContact].acks  = ( LPINT )mir_calloc( sizeof( INT ) * ( INT )ack->hProcess );
							pPs->infosUpdated[iSubContact].count = ( INT )ack->hProcess;
						}
					
						if( ack->result == ACKRESULT_SUCCESS || ack->result == ACKRESULT_FAILED )
							pPs->infosUpdated[iSubContact].acks[ack->lParam] = 1;

						// check for pending tasks
						for( iSubContact = 0; iSubContact < pPs->nSubContacts; iSubContact++ ) {
							for( i = 0; i < pPs->infosUpdated[iSubContact].count; i++ ) {
								if( pPs->infosUpdated[iSubContact].acks[i] == 0 )
									break;
							}
							if( i < pPs->infosUpdated[iSubContact].count )
								break;
						}
					}
						
					// all acks are done, finish updating
					if( iSubContact >= pPs->nSubContacts ) {
						ResetUpdateInfo( pPs );
						ShowWindow( GetDlgItem( hDlg, TXT_UPDATING ), SW_HIDE );
						KillTimer( hDlg, TIMERID_UPDATING );
						DlgProc( hDlg, M_CHECKONLINE, NULL, NULL );
					}
			}
			break;
		}

		/**
		 * name:	HM_SETTING_CHANGED
		 * desc:	This message is called by the ME_DB_CONTACT_SETTINGCHANGED event and forces all
		 *			unedited settings in the propertysheetpages to be updated
		 * param:	wParam	- handle to the contact whose settings are to be changed
		 *			lParam	- DBCONTACTWRITESETTING structure that identifies the changed setting
		 * return:	FALSE
		 **/
		case HM_SETTING_CHANGED:
			if( !( pPs->dwFlags & PSF_LOCKED ) ) {
				HANDLE hContact = ( HANDLE )wParam;
				DBCONTACTWRITESETTING* pdbcws = ( DBCONTACTWRITESETTING* )lParam;

				if( hContact != pPs->hContact ) {
					if( !myGlobals.szMetaProto )
						break;
					if( pPs->hContact != ( HANDLE )CallService( MS_MC_GETMETACONTACT, ( WPARAM )hContact, NULL ) )
						break;
					if( !DB::Setting::GetByte( SET_META_SCAN, TRUE ) )
						break;
				}

				if(
					!mir_stricmp( pdbcws->szSetting, SET_CONTACT_MYHANDLE ) ||
					!mir_stricmp( pdbcws->szSetting, SET_CONTACT_NICK )
				  )
				{
					// force the update of all propertysheetpages
					DlgProc( hDlg, PSM_FORCECHANGED, NULL, NULL );
					// update the windowtitle
					DlgProc( hDlg, HM_SETWINDOWTITLE, NULL, lParam );
				}
				else if(
					!mir_stricmp( pdbcws->szModule, USERINFO ) ||
					!mir_stricmp( pdbcws->szModule, pPs->szBaseProto ) ||
					!mir_stricmp( pdbcws->szModule, MOD_MBIRTHDAY )
				  )
				{
					// force the update of all propertysheetpages
					DlgProc( hDlg, PSM_FORCECHANGED, NULL, NULL );
				}
			}
			break;

		case WM_NOTIFY:
			switch( wParam ) {

				//
				// Notification Messages sent by the TreeView
				//
				case STATIC_TREE:
					switch( (( LPNMHDR )lParam )->code ) {
						case TVN_SELCHANGING:
						{   
							pPs->dwFlags |= PSF_LOCKED;
							pPs->pTree->OnSelChanging();
							pPs->dwFlags &= ~PSF_LOCKED;
							break;
						}

						case TVN_SELCHANGED:
							if( pPs->dwFlags & PSF_INITIALIZED ) {
								pPs->dwFlags |= PSF_LOCKED;
								pPs->pTree->OnSelChanged( ( LPNMTREEVIEW )lParam );
								pPs->dwFlags &= ~PSF_LOCKED;
							}
							break;

						case TVN_BEGINDRAG:
						{
							LPNMTREEVIEW nmtv = ( LPNMTREEVIEW )lParam;

							if( nmtv->itemNew.hItem == TreeView_GetSelection( nmtv->hdr.hwndFrom ) ) {
								SetCapture( hDlg );
								pPs->pTree->BeginDrag( nmtv->itemNew.hItem );
							}
							TreeView_SelectItem( nmtv->hdr.hwndFrom, nmtv->itemNew.hItem );
							break;
						}

						case TVN_ITEMEXPANDED:
							pPs->pTree->AddFlags( PSTVF_STATE_CHANGED );
							break;

						case NM_KILLFOCUS:
							KillTimer( hDlg, TIMERID_RENAME );
							break;

						case NM_CLICK:
						{
							TVHITTESTINFO hti;

							GetCursorPos( &hti.pt );
							ScreenToClient( pPs->pTree->Window(), &hti.pt );
							TreeView_HitTest( pPs->pTree->Window(), &hti );
							if( ( hti.flags & ( TVHT_ONITEM|TVHT_ONITEMRIGHT ) ) && hti.hItem == TreeView_GetSelection( pPs->pTree->Window() ) ) 
								SetTimer( hDlg, TIMERID_RENAME, 500, NULL );
							break;
						}

						case NM_RCLICK:
							pPs->pTree->PopupMenu();
							return 0;

						case TVN_KEYDOWN: 
						{
							LPNMTVKEYDOWN lpnmk = ( LPNMTVKEYDOWN )lParam;

							switch( lpnmk->wVKey ) {
								case VK_F2:
									KillTimer( hDlg, TIMERID_RENAME );
									pPs->pTree->BeginLabelEdit( NULL );
									break;
							}
							break;
						}
					}
					break;
			}
			break;
		
		case WM_MOUSEMOVE:
			if( pPs->pTree->IsDragging() ) {   
				TVHITTESTINFO hti;
	                
				hti.pt.x = ( SHORT )LOWORD( lParam );
				hti.pt.y = ( SHORT )HIWORD( lParam );
				MapWindowPoints( hDlg, pPs->pTree->Window(), &hti.pt, 1 );
				TreeView_HitTest( pPs->pTree->Window(), &hti );
				
				if( hti.flags & ( TVHT_ONITEM|TVHT_ONITEMRIGHT ) ) {
					RECT rc;
					BYTE height;

					// check where over the item, the pointer is
					if( TreeView_GetItemRect( pPs->pTree->Window(), hti.hItem, &rc, FALSE ) ) {
						height = ( BYTE )( rc.bottom - rc.top );

						if( hti.pt.y - ( height / 3 ) < rc.top ) {
							SetCursor( LoadCursor( NULL, IDC_ARROW ) );
							TreeView_SetInsertMark( pPs->pTree->Window(), hti.hItem, 0 );
						}
						else
						if( hti.pt.y + ( height / 3 ) > rc.bottom ) {
							SetCursor( LoadCursor( NULL, IDC_ARROW ) );
							TreeView_SetInsertMark( pPs->pTree->Window(), hti.hItem, 1 );
						}
						else {
							TreeView_SetInsertMark( pPs->pTree->Window(), NULL, 0 );
							SetCursor( LoadCursor( ghInst, MAKEINTRESOURCE( CURSOR_ADDGROUP ) ) );
						}
					}
				}
				else {
					if( hti.flags & TVHT_ABOVE ) SendMessage( pPs->pTree->Window(), WM_VSCROLL, MAKEWPARAM( SB_LINEUP, 0 ), 0 );
					if( hti.flags & TVHT_BELOW ) SendMessage( pPs->pTree->Window(), WM_VSCROLL, MAKEWPARAM( SB_LINEDOWN, 0 ), 0 );
					TreeView_SetInsertMark( pPs->pTree->Window(), NULL, 0 );
				}
			}
			break;
		
		case WM_LBUTTONUP:

			// drop item
			if( pPs->pTree->IsDragging() ) {
				TVHITTESTINFO hti;              
				RECT rc;
				BYTE height;
				BOOLEAN bAsChild = FALSE;

				TreeView_SetInsertMark( pPs->pTree->Window(), NULL, 0 );
				ReleaseCapture();
				SetCursor( LoadCursor( NULL, IDC_ARROW ) );
				
				hti.pt.x = ( SHORT )LOWORD( lParam );
				hti.pt.y = ( SHORT )HIWORD( lParam );
				MapWindowPoints( hDlg, pPs->pTree->Window(), &hti.pt, 1 );
				TreeView_HitTest( pPs->pTree->Window(), &hti );

				if( hti.hItem == pPs->pTree->DragItem() ) {
					pPs->pTree->EndDrag();
					break;
				}

				if( hti.flags & TVHT_ABOVE ) {
					hti.hItem = TVI_FIRST;
				}
				else
				if( hti.flags & ( TVHT_NOWHERE|TVHT_BELOW ) ) {
					hti.hItem = TVI_LAST;
				}
				else
				if( hti.flags & ( TVHT_ONITEM|TVHT_ONITEMRIGHT ) ) {
					// check where over the item, the pointer is
					if( !TreeView_GetItemRect( pPs->pTree->Window(), hti.hItem, &rc, FALSE ) ) {
						pPs->pTree->EndDrag();
						break;
					}
					height = ( BYTE )( rc.bottom - rc.top );

					if( hti.pt.y - ( height / 3 ) < rc.top ) {
						HTREEITEM hItem = hti.hItem;

						if( !( hti.hItem = TreeView_GetPrevSibling( pPs->pTree->Window(), hItem ) ) ) {
							if( !( hti.hItem = TreeView_GetParent( pPs->pTree->Window(), hItem ) ) )
								hti.hItem = TVI_FIRST;
							else
								bAsChild = TRUE;
						}
					}
					else 
					if( hti.pt.y + ( height / 3 ) <= rc.bottom ) {
						bAsChild = TRUE;
					}
				}	
				pPs->pTree->MoveItem( pPs->pTree->DragItem(), hti.hItem, bAsChild );
				pPs->pTree->EndDrag();

			}
			break; 
		
		case WM_COMMAND:
			switch( LOWORD( wParam ) ) {
				case IDCANCEL:
				{   
					pPs->pTree->OnCancel();
					DestroyWindow( hDlg );
					break;
				}

				/**
				 *	name:	IDOK / IDAPPLY
				 *	desc:	user clicked on apply or ok button in order to save changes
				 **/
				case IDOK:
				case IDAPPLY:
					if( pPs->dwFlags & PSF_CHANGED ) {   
						// kill focus from children to make sure all data can be saved ( ComboboxEx )
						SetFocus( hDlg );

						pPs->dwFlags |= PSF_LOCKED;
						if( pPs->pTree->OnApply() ) {
							pPs->dwFlags &= ~( PSF_LOCKED|PSF_CHANGED );
							break;
						}

						pPs->dwFlags &= ~PSF_CHANGED;
						EnableWindow( GetDlgItem( hDlg, IDAPPLY ), FALSE );
						CallService( MS_CLIST_INVALIDATEDISPLAYNAME, ( WPARAM )pPs->hContact, NULL );

						// need to upload owners settings
						if( !pPs->hContact && myGlobals.CanChangeDetails && DB::Setting::GetByte( SET_PROPSHEET_CHANGEMYDETAILS, FALSE ) ) {
							if( pPs->pUpload = new CPsUpload( pPs, LOWORD( wParam ) == IDOK ) ) {
								if( pPs->pUpload->UploadFirst() == CPsUpload::UPLOAD_CONTINUE )
									break;
								MIR_DELETE( pPs->pUpload );
							}
						}
						pPs->dwFlags &= ~PSF_LOCKED;
					}
					if( LOWORD( wParam ) == IDOK ) 
						DestroyWindow( hDlg );
					break;

				case BTN_UPDATE:
					{
						if( pPs->hContact != NULL ) 
						{
							ResetUpdateInfo( pPs );

							mir_snprintfA( pPs->szUpdating, SIZEOF( pPs->szUpdating ), 
								"%s ( %s )", Translate( "Updating" ), pPs->szBaseProto );
							
							// need meta contact's subcontact information
							if( DB::Module::IsMetaAndScan( pPs->szBaseProto ) ) 
							{
								HANDLE hSubContact;
								CHAR szService[MAXSETTING];
								INT	i, numSubs;
								
								numSubs = DB::MetaContact::SubCount( pPs->hContact );
								
								// count valid subcontacts whose protocol supports the PSS_GETINFO service to update the information
								for( i = 0; i < numSubs; i++ ) 
								{
									hSubContact = DB::MetaContact::Sub( pPs->hContact, i );
									if( hSubContact != NULL ) 
									{
										mir_snprintfA( szService, SIZEOF( szService ), "%s%s", 
											DB::Contact::Proto( hSubContact ), PSS_GETINFO );
										
										if( ServiceExists( szService ) ) 
										{
											pPs->infosUpdated = ( TAckInfo* )mir_realloc( pPs->infosUpdated, sizeof( TAckInfo ) * ( pPs->nSubContacts + 1 ) );
											pPs->infosUpdated[pPs->nSubContacts].hContact = hSubContact;
											pPs->infosUpdated[pPs->nSubContacts].acks = NULL;
											pPs->infosUpdated[pPs->nSubContacts].count = 0;
											pPs->nSubContacts++;
										}
									}
								}

								if( pPs->nSubContacts != 0 )
								{
									BOOLEAN bDo = FALSE;

									// call the services
									for( i = 0; i < pPs->nSubContacts; i++ ) 
									{
										if( !CallContactService( pPs->infosUpdated[pPs->nSubContacts].hContact, PSS_GETINFO, NULL, NULL ) )
										{
											bDo = TRUE;
										}
									}
									if( bDo )
									{
										EnableWindow( GetDlgItem( hDlg, BTN_UPDATE ), FALSE );
										ShowWindow( GetDlgItem( hDlg, TXT_UPDATING ), SW_SHOW );
										SetTimer( hDlg, TIMERID_UPDATING, 100, NULL );
									}
								}
							}
							else if( !CallContactService( pPs->hContact, PSS_GETINFO, NULL, NULL ) ) 
							{
								pPs->infosUpdated = ( TAckInfo* )mir_calloc( sizeof( TAckInfo ) );
								pPs->infosUpdated[0].hContact = pPs->hContact;
								pPs->nSubContacts = 1;

								EnableWindow( GetDlgItem( hDlg, BTN_UPDATE ), FALSE );
								ShowWindow( GetDlgItem( hDlg, TXT_UPDATING ), SW_SHOW );
								SetTimer( hDlg, TIMERID_UPDATING, 100, NULL );
							}
						}
					}
					break;

				case BTN_IMPORT:
					NExImport::Import( pPs->hContact, hDlg );
					break;

				case BTN_EXPORT:
					// save changes before exporting data
					DlgProc( hDlg, WM_COMMAND, MAKEWPARAM( IDAPPLY, BN_CLICKED ), ( LPARAM )GetDlgItem( hDlg, IDAPPLY ) );
					// do the exporting stuff
					NExImport::Export( pPs->hContact, hDlg );
					break;
			}
			break;

		case WM_CLOSE:
			DlgProc( hDlg, WM_COMMAND, MAKEWPARAM( IDCANCEL, BN_CLICKED ), ( LPARAM )GetDlgItem( hDlg, IDCANCEL ) );
			break;

		case WM_DESTROY:
		{
			INT i = 0;

			ResetUpdateInfo( pPs );

			// avoid any further message processing for this dialog page
			WindowList_Remove( ghWindowList, hDlg );
			SetUserData( hDlg, NULL );

			// unhook events and stop timers
			KillTimer( hDlg, TIMERID_RENAME );
			UnhookEvent( pPs->hProtoAckEvent );
			UnhookEvent( pPs->hSettingChanged );
			UnhookEvent( pPs->hIconsChanged );
			
			// save my window position
			Utils_SaveWindowPosition( hDlg, NULL, MODNAME, "DetailsDlg" );

			// save current tree and destroy it
			if( pPs->pTree != NULL ) {
				// save tree's current look
				pPs->pTree->SaveState();
				delete pPs->pTree;
				pPs->pTree = NULL;
			}

			DeleteObject( pPs->hBoldFont );
			free( pPs ); pPs = NULL;
			MagneticWindows_RemoveWindow(hDlg);
			break;
		}
	}
	return FALSE;
}

} // namespace NContactDetailsPS
